﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Input;
using System.Linq.Expressions;
using PwdMgrUI.Objects;

namespace PwdMgrUI.ViewModels
{
    internal abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public event EventHandler<CommandNotificationEventArgs> CommandFinished;

        protected ViewModelBase()
        {
            InitCommands();
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void OnCommandFinished(
            string identifier,
            CommandResult result,
            IDictionary<CommandError, string> errors)
        {
            if (CommandFinished != null)
            {
                CommandFinished(this, new CommandNotificationEventArgs(
                    identifier, result, errors));
            }
        }

        protected virtual void InitCommands()
        {
            try
            {
                var properties = this.GetType().GetProperties(
                    BindingFlags.Public | BindingFlags.Instance);

                foreach (var property in properties)
                {
                    if (property.PropertyType != typeof(ICommand))
                        continue;

                    var commands =
                        property.GetCustomAttributes(typeof(CommandAttribute), true);

                    if (commands.Length == 0)
                        continue;

                    CommandAttribute cmd = (CommandAttribute)commands[0];


                    Action<object> exec = BindExecFunc(property, cmd);
                    Func<object, bool> canExec = BindCanExecFunc(property, cmd);

                    property.SetValue(this, new DefaultCommand(exec, canExec), null);
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        private Action<object> BindExecFunc(PropertyInfo prop,
            CommandAttribute cmdAttribute)
        {
            string execFuncName = cmdAttribute.ExecFuncName;

            MethodInfo method = this.GetType().GetMethod(execFuncName,
                BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            if (method == null)
                throw new InvalidOperationException("Cannot find the command execute function");

            Action<object> exec =
                (Action<object>)Delegate.CreateDelegate(typeof(Action<object>),
                this, method);

            return exec;
        }

        private Func<object, bool> BindCanExecFunc(PropertyInfo prop,
            CommandAttribute cmdAttribute)
        {
            string canExecFuncName = cmdAttribute.CanExecFuncName;

            if (string.IsNullOrEmpty(canExecFuncName))
                return null;

            MethodInfo method = this.GetType().GetMethod(canExecFuncName,
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            if (method == null)
                throw new InvalidOperationException("Cannot find canExecFuncName.");

            Func<object, bool> func =
                (Func<object, bool>)Delegate.CreateDelegate(
                    typeof(Func<object, bool>), this, method);

            return func;
        }

        public class DefaultCommand : ICommand
        {
            public DefaultCommand(Action<object> execute,
                Func<object, bool> canExecute)
            {
                _execute = execute;
                _canExecute = canExecute;
            }

            private Func<object, bool> _canExecute;
            private Action<object> _execute;
            public bool CanExecute(object parameter)
            {
                if (_canExecute != null)
                {
                    return _canExecute(parameter);
                }

                return true;
            }

            public event EventHandler CanExecuteChanged;

            public void OnCanExecuteChanged()
            {
                if (CanExecuteChanged != null)
                    CanExecuteChanged(this, new EventArgs());
            }

            public void Execute(object parameter)
            {
                if (_execute != null)
                    _execute(parameter);
            }
        }

    }
}
